#ifndef UTILS_MEMORY_POOL_H
#define UTILS_MEMORY_POOL_H

#include <cstdint>

namespace Utils
{
	class MemoryPool2 
	{
	public:
	MemoryPool2::MemoryPool2(const uint32_t howMany, const size_t size)
		: mNextFreeBlock(nullptr)
		, mBlockSize((sizeof(MemoryBlock*) <= size) ? size : sizeof(MemoryBlock*))
		, mNumberOfBlocksToExpand(howMany)
	{
		assert(0 < howMany && "MemoryPool2::MemoryPool2: The number of blocks cannot be zero.");
		assert(0 < size && "MemoryPool2::MemoryPool2: The size of blocks cannot be zero.");
	
		ExpandTheFreeList();
	}
	
	~MemoryPool2();
	
	public:
		inline void* Alloc(const size_t size);
		inline void Free(void *someElement);
	
	private:
		struct MemoryBlock
		{
			MemoryBlock *mNextBlock;		
		}
		
		MemoryBlock *mNextFreeBlock; // Next element on the free list.
		
		const size_t mBlockSize; // Size of each block to expand.
		const uint32_t mNumberOfBlocksToExpand; // Number of blocks to expand in the pool.
		
		// Add free elements to the free list.
		void ExpandTheFreeList();
	}
	
	inline void* MemoryPool2::Alloc(const size_t size)
	{
		assert(size <= mBlockSize && "MemoryPool2::Alloc: The requested size is bigger than the block size");
	
		// If there are not free blocks, we need to 
		// expand the pool.
		if(!mNextFreeBlock) 
			ExpandTheFreeList();
		
		MemoryBlock * const head = mNextFreeBlock;
		mNextFreeBlock = head->mNextBlock;
			
		return head;
	}
	
	inline void MemoryPool2::Free(void *someElement)
	{
		MemoryBlock * const head = static_cast <MemoryBlock*> (someElement);
		head->mNextBlock = mNextFreeBlock;
		mNextFreeBlock = head;
	}
	
    class MemoryChunk 
	{
	public:
		MemoryChunk(MemoryChunk * const nextChunk, const size_t requestedSize);
		~MemoryChunk() { delete[] mMemory; }
	
	public:
		void* Alloc(size_t requestedSize)
		{
			void *address = static_cast<void*> (static_cast <size_t> (mMemory) + mBytesAlreadyAllocated);
			
			// Keep track of available space by updating
			// the number of bytes already allocated in this chunk.
			bytesAlreadyAllocated += requestedSize;
			
			return address;
		}
		
		// We do not bother freeing memory fragments on the fly.
		// The whole memory chunk gets deallocated and sent back
		// to the heap when the object is deleted.
		void Free (void* someElement) {} 
	
		// Pointer to next memory chunk on the list.
		MemoryChunk* GetNextMemoryChunk() const { return mNext; }
	
		// How much space do we have left on this memory chunk?
		size_t GetAvailableSpace() const { return mChunkSize - mBytesAlreadyAllocated; }
	
		// This is the default size of a single memory chunk.
		static const size_t sDefaultChunkSize = 4096;
	
	private:
		// We do not let copy.
		MemoryChunk(const MemoryChunk& memoryChunk);
		MemoryChunk& operator=(const MemoryChunk& memoryChunk);
	
		const size_t mChunkSize; // The size of a single memory chunk.
		
		MemoryChunk *mNext; // Next chunk in the list.
		void *mMemory; // Memory of this chunk.
		
		size_t mBytesAlreadyAllocated; // This many bytes already allocated on the current memory chunk.
	};
	
	class ByteMemoryPool 
	{
	public:
		ByteMemoryPool(const size_t initSize = MemoryChunk::sDefaultChunkSize)
		{
			// Construct the ByteMemoryPool object. Build the private storage.
			expandStorage(initSize);
		}
		
		~ByteMemoryPool();
	
	public:
		// Allocate memory from private pool.
		void* Alloc(const size_t requestedSize)
		{
			// Check if we hace enough space available and
			// expand storage if necessary.
			const size_t space = listOfMemoryChunks->GetAvailableSpace();
			if(space < requestedSize) 
				expandStorage(requestSize);
				
			return listOfMemoryChunks->Alloc(requestSize);
		}
		
		// Free memory previously allocated from the pool.
		void Free (void* someElement)
		{
			listOfMemoryChunks->free(doomed);
		}
	
	private:
		MemoryChunk *listOfMemoryChunks; // A list of memory chunks. This is our private storage.
		
		// Add one memory chunk to our private storage
		void expandStorage(const size_t requestedSize);
	};
	
	class MemoryPool
	{
	private:
		//The type of the node of linkedlist.
		struct MemoryUnit 
		{
			MemoryUnit *mPrevious;
			MemoryUnit *mNext;
		};

	public:
		MemoryPool(const uint32_t unitNum = 50, const uint32_t unitSize = 1024);
		
		~MemoryPool()
		{
			free(mMemBlock);
		}

	public:
		void* Alloc(const uint32_t size);	// Allocate memory unit
		void Free(void *p); // Free memory unit
	
	private:
		void *mMemBlock; //The address of memory pool.

		// Manage all unit with two linkedlist.
		MemoryUnit *mAllocatedMemBlock;	// Head pointer to Allocated linkedlist.
		MemoryUnit *mFreeMemBlock; // Head pointer to Free linkedlist.

		uint32_t mUnitSize;	// Memory unit size. There are much unit in memory pool.
		uint32_t mBlockSize; // Memory pool size. Memory pool is make of memory unit.
	};
};

#endif // UTILS_MEMORY_POOL_H
